#ifndef MENU_H_
#define MENU_H_

#include <gtkmm.h>
#include <map>
#include <boost/ptr_container/ptr_list.hpp>

#include "conf/collection.h"
#include "conf/attribute.h"
#include "yellowpage.h"
#include "widgets.h"

namespace gpyp {

class Application;
class MainWindow;
class ChannelList;

namespace pref {
class PlayerDialog;
}

class MenuItem;
class Menu;

class MenuData : boost::noncopyable {
public:
  typedef ConfMenu::RefMenu RefData;

private:
  RefData data_;

public:
  inline RefData get_data() {
    return data_;
  }

  inline MenuData(RefData data) :
    data_(data)
  {
  }
  virtual ~MenuData();
};

class MenuShell : public MenuData {
public:
  typedef sigc::slot<void, CommandOpts &> SlotActivate;

private:
  Application &app_;
  SlotActivate slot_activate_;

  sigc::connection conn_item_added_;
  sigc::connection conn_item_removed_;
  sigc::connection conn_item_reordered_;
  sigc::connection conn_cleared_;

protected:
  inline Application &get_app() {
    return app_;
  }
  inline SlotActivate get_slot_activate() {
    return slot_activate_;
  }

  virtual void on_item_added(RefData data) = 0;
  virtual void on_item_removed(RefData data) = 0;
  virtual void on_item_reordered(RefData data,
                                 RefData prev,
                                 RefData next) = 0;
  virtual void on_cleared() = 0;

  virtual void on_activate(CommandOpts &opts);

  void run_command();

  Gtk::MenuItem *create_menu_item(RefData data);
  Gtk::Menu *create_menu();

public:
  MenuShell(Application &app,
            RefData      data,
            SlotActivate slot_activate = SlotActivate());
  virtual ~MenuShell();
};

class MenuItem : public MenuShell,
                 public Gtk::ImageMenuItem {
private:
  Gtk::Image image_;

  sigc::connection conn_icon_changed_;
  sigc::connection conn_im_special_icon_changed_;

  void im_on_special_icon_changed(const char *icon_name);

protected:
  virtual void on_item_added(RefData data);
  virtual void on_item_removed(RefData data);
  virtual void on_item_reordered(RefData data,
                                 RefData prev,
                                 RefData next);
  virtual void on_cleared();

  virtual void on_activate();

  virtual void icon_on_changed();

public:
  MenuItem(Application &app,
           RefData      data,
           SlotActivate slot_activate);
  virtual ~MenuItem();
};

class SpecialMenuItem : public MenuItem {
private:
  sigc::connection conn_sptype_changed_;

protected:
  virtual void on_item_added(RefData data);
  virtual void on_item_removed(RefData data);
  virtual void on_cleared();

  void sptype_on_changed();

public:
  SpecialMenuItem(Application &app,
                  RefData      data,
                  SlotActivate slot_activate);
  virtual ~SpecialMenuItem();
};

class SimpleMenuItem : public Gtk::ImageMenuItem {
private:
  conf::RefAttributeStr attr_icon_;
  sigc::connection      conn_attr_icon_changed_;

  Gtk::Image image_;

  sigc::connection conn_activate_;

protected:
  virtual void attr_icon_on_changed();

public:
  SimpleMenuItem(const Glib::ustring &label,
                 sigc::slot<void>     slot,
                 const Glib::ustring &icon_name = Glib::ustring());
  SimpleMenuItem(const Glib::ustring       &label,
                 sigc::slot<void>           slot,
                 const Gtk::BuiltinStockID &stock_id);
  SimpleMenuItem(conf::RefAttributeStr attr_label,
                 conf::RefAttributeStr attr_icon);
  virtual ~SimpleMenuItem();
};

class SeparatorMenuItem : public MenuData,
                          public Gtk::SeparatorMenuItem {
public:
  SeparatorMenuItem(RefData data);
};

class CheckMenuItem : public Gtk::CheckMenuItem {
public:
  inline CheckMenuItem(const Glib::ustring   &label,
                       conf::RefAttributeBool attr) :
    Gtk::CheckMenuItem(label, true)
  {
    attr->attach(property_active());
    show();
  }
};

template<typename T>
class RadioMenuItem : public Gtk::CheckMenuItem {
public:
  typedef conf::RefAttributeEnum<T> AttrType;
  typedef typename T::ValueType ValueType;

private:
  AttrType  attr_;
  ValueType value_;

  sigc::connection conn_attr_changed_;
  sigc::connection conn_toggled_;

protected:
  inline void attr_on_changed() {
    conn_toggled_.block();
    set_active(*attr_ == value_);
    conn_toggled_.unblock();
  }
  inline void _on_toggled() {
    if (get_active()) {
      conn_attr_changed_.block();
      attr_ = value_;
      conn_attr_changed_.unblock();
    }
  }

public:
  inline RadioMenuItem(const Glib::ustring &label,
                       AttrType             attr,
                       ValueType            value) :
    Gtk::CheckMenuItem(label, true),
    attr_             (attr),
    value_            (value)
  {
    attr_on_changed();
    conn_attr_changed_ = attr_->signal_changed()
      .connect(sigc::mem_fun(*this, &RadioMenuItem::attr_on_changed));
    conn_toggled_ = signal_toggled()
      .connect(sigc::mem_fun(*this, &RadioMenuItem::_on_toggled));
    set_draw_as_radio(true);
    show();
  }
  inline virtual ~RadioMenuItem() {
    conn_attr_changed_.disconnect();
    conn_toggled_     .disconnect();
  }
};

class MenuBase : public Gtk::Menu {
protected:
  struct PointerMatch {
    bool operator()(Gtk::MenuItem &mi1, Gtk::MenuItem *mi2) {
      return &mi1 == mi2;
    }
  };

private:
  size_t item_count_front_;
  size_t item_count_back_;

  Gtk::SeparatorMenuItem *separator_front_;
  Gtk::SeparatorMenuItem *separator_back_;

protected:
  void prepend(Gtk::MenuItem &item);
  void add(Gtk::MenuItem &item);
  void append(Gtk::MenuItem &item);

  template<typename T, typename F>
  void remove(T data, F match) {
    MenuList &items = Gtk::MenuShell::items();
    for(MenuList::iterator iter = items.begin();
        iter != items.end(); ++iter) {
      if (match(*iter, data)) {
        items.erase(iter);
        break;
      }
    }
  }
  void remove(Gtk::MenuItem &target);
  void reorder(int tpos, int ppos, int npos);
  template<typename T, typename F>
  void reorder(T target, T prev, T next, F match) {
    int tpos = -1, ppos = -1, npos = -1, i = 0;
    MenuList &items = Gtk::MenuShell::items();
    for(MenuList::iterator iter = items.begin();
        iter != items.end(); ++iter) {
      if      (match(*iter, target)) tpos = i;
      else if (match(*iter, prev))   ppos = i;
      else if (match(*iter, next))   npos = i;
      ++i;
    }
    reorder(tpos, ppos, npos);
  }
  void reorder(Gtk::MenuItem &target,
               Gtk::MenuItem *prev,
               Gtk::MenuItem *next);
  void clear();

  inline Gtk::SeparatorMenuItem *get_separator_front() {
    return separator_front_;
  }
  inline Gtk::SeparatorMenuItem *get_separator_back() {
    return separator_back_;
  }

public:
  MenuBase();
};

class Menu : public MenuShell,
             public MenuBase {
protected:
  struct DataMatch {
    inline bool operator()(Gtk::MenuItem &mi, RefData data) {
      MenuData *md = dynamic_cast<MenuData *>(&mi);
      return md && md->get_data() == data;
    }
  };

protected:
  virtual void on_item_added(RefData data);
  virtual void on_item_removed(RefData data);
  virtual void on_item_reordered(RefData data, RefData prev, RefData next);
  virtual void on_cleared();

public:
  Menu(Application &app,
       RefData      data,
       SlotActivate slot_activate = SlotActivate());
};

template<typename T>
class CollectionMenu : public MenuBase {
public:
  typedef boost::shared_ptr<T> RefData;
  typedef typename T::RefItem  RefItem;
  typedef sigc::slot<void, RefItem, CommandOpts &> SlotActivate;

private:
  struct Activator {
    RefItem      item_;
    SlotActivate slot_activate_;
    void on_activate(CommandOpts &opts) {
      slot_activate_(item_, opts);
    }
    Activator(RefItem      item,
              SlotActivate slot_activate) :
      item_         (item),
      slot_activate_(slot_activate)
    {
    }
  };
  typedef boost::shared_ptr<Activator> RefActivator;

  typedef std::map<RefItem, std::pair<Gtk::MenuItem *, RefActivator> > ItemMap;

private:
  Application      &app_;
  RefData           data_;
  ConfMenu::RefMenu menu_;
  SlotActivate      slot_activate_;

  ItemMap item_map_;

  sigc::connection conn_item_added_;
  sigc::connection conn_item_removed_;
  sigc::connection conn_item_reordered_;
  sigc::connection conn_cleared_;

  Gtk::MenuItem *create_menu_item(RefItem item) {
    RefActivator activator(new Activator(item, sigc::mem_fun(*this, &CollectionMenu<T>::on_activate)));
    Gtk::MenuItem *mi = create_item(item);
    mi->set_submenu(*Gtk::manage(new gpyp::Menu(app_, menu_, sigc::mem_fun(*activator, &Activator::on_activate))));
    item_map_[item] = std::make_pair(mi, activator);
    return mi;
  }

protected:
  virtual Gtk::MenuItem *create_item(RefItem item) = 0;

  virtual void on_item_added(RefItem item) {
    add(*create_menu_item(item));
  }
  virtual void on_item_removed(RefItem item) {
    typename ItemMap::iterator iter = item_map_.find(item);
    remove(*(iter->second.first));
    item_map_.erase(iter);
  }
  virtual void on_item_reordered(RefItem item, RefItem prev, RefItem next) {
    Gtk::MenuItem *target = item_map_[item].first;
    reorder(*target,
            prev ? (item_map_[prev].first) : NULL,
            next ? (item_map_[next].first) : NULL);
  }
  virtual void on_cleared() {
    clear();
  }

  virtual void on_activate(RefItem item, CommandOpts &opts) {
    if (!slot_activate_.empty()) slot_activate_(item, opts);
  }

public:
  CollectionMenu(Application      &app,
                 RefData           data,
                 ConfMenu::RefMenu menu,
                 SlotActivate      slot_activate = SlotActivate()) :
    app_          (app),
    data_         (data),
    menu_         (menu),
    slot_activate_(slot_activate)
  {
    conn_item_added_ = data_->signal_item_added()
      .connect(sigc::mem_fun(*this, &CollectionMenu<T>::on_item_added));
    conn_item_removed_ = data_->signal_item_removed()
      .connect(sigc::mem_fun(*this, &CollectionMenu<T>::on_item_removed));
    conn_item_reordered_ = data_->signal_item_reordered()
      .connect(sigc::mem_fun(*this, &CollectionMenu<T>::on_item_reordered));
    conn_cleared_ = data_->signal_cleared()
      .connect(sigc::mem_fun(*this, &CollectionMenu<T>::on_cleared));
  }
  virtual ~CollectionMenu() {
    conn_item_added_    .disconnect();
    conn_item_removed_  .disconnect();
    conn_item_reordered_.disconnect();
    conn_cleared_       .disconnect();
  }
};

class YPMenu : public CollectionMenu<YPCollection> {
private:
  class MenuItem : public SimpleMenuItem {
  private:
    RefYellowPage yp_;

  protected:
    void yp_enable_on_changed();

  public:
    MenuItem(RefYellowPage yp);
  };

private:
  SimpleMenuItem reload_all_;

protected:
  virtual Gtk::MenuItem *create_item(RefYellowPage yp);

  virtual void on_activate(RefYellowPage yp, CommandOpts &opts);

public:
  YPMenu(Application &app);
};

class ChannelMenu : public Menu {
private:
  Application &app_;

protected:
  virtual void on_activate(CommandOpts &opts);

public:
  ChannelMenu(Application &app);
};

class ToolMenu : public Menu {
private:
  Application &app_;

protected:
  virtual void on_activate(CommandOpts &opts);

public:
  ToolMenu(Application &app);
};

class StatusIconMenu : public ToolMenu {
private:
  SimpleMenuItem quit_;

public:
  StatusIconMenu(Application &app);
};

class ViewMenu : public Gtk::Menu {
private:
  MainWindow &mainwindow_;

  RadioMenuItem<AttributeCLModel> model_default_;
  RadioMenuItem<AttributeCLModel> model_played_;
  RadioMenuItem<AttributeCLModel> model_ended_;

  RadioMenuItem<AttributePanelPos> panel_left_;
  RadioMenuItem<AttributePanelPos> panel_right_;
  RadioMenuItem<AttributePanelPos> panel_top_;
  RadioMenuItem<AttributePanelPos> panel_bottom_;
  RadioMenuItem<AttributePanelPos> panel_none_;

  CheckMenuItem menubar_;
  CheckMenuItem filterbar_;
  CheckMenuItem statusbar_;

public:
  ViewMenu(MainWindow &mainwindow, ChannelList &channellist);
};

class SettingsMenu : public Gtk::Menu {
private:
  CheckMenuItem  auto_play_;
  CheckMenuItem  mute_;
  CheckMenuItem  resizable_;
  SimpleMenuItem pref_;
  SimpleMenuItem config_view_;
  SimpleMenuItem save_;

public:
  SettingsMenu(Application &app, MainWindow &mainwindow);
};

class HelpMenu : public Gtk::Menu {
private:
  MainWindow &mainwindow_;

  SimpleMenuItem mi_bc_;
  SimpleMenuItem mi_opts_;
  SimpleMenuItem mi_env_info_;
#ifdef ENABLE_DBUS_SERVICE
  SimpleMenuItem mi_play_script_;
#endif
  SimpleMenuItem mi_about_;

#ifdef ENABLE_DBUS_SERVICE
  void play_script();
#endif

public:
  HelpMenu(MainWindow &mainwindow);
};

class ChannelListMenu : public ChannelMenu {
private:
  MainWindow   &mainwindow_;

  Gtk::MenuItem mi_yp_;
  YPMenu        menu_yp_;
  Gtk::MenuItem mi_tool_;
  ToolMenu      menu_tool_;
  Gtk::MenuItem mi_view_;
  ViewMenu      menu_view_;
  Gtk::MenuItem mi_settings_;
  SettingsMenu  menu_settings_;
  Gtk::MenuItem mi_help_;
  HelpMenu      menu_help_;

  sigc::connection conn_show_menubar_changed_;

  void show_menubar_changed();

public:
  ChannelListMenu(Application &app,
                  MainWindow  &mainwindow,
                  ChannelList &channellist);
  virtual ~ChannelListMenu();
};

class ColorSelectMenu : public Gtk::Menu {
private:
  sigc::signal<void, const Color &> signal_selected_;
public:
  inline sigc::signal<void, const Color &> signal_selected() {
    return signal_selected_;
  }

private:
  class Item : public Gtk::RadioMenuItem {
  private:
    sigc::signal<void, const Color &> signal_selected_;
  public:
    inline sigc::signal<void, const Color &> signal_selected() {
      return signal_selected_;
    }

  private:
    Gtk::EventBox box_;
    Gtk::Label    label_;
    Color         color_;

  protected:
    virtual void on_toggled();

  public:
    inline const Color &color() {
      return color_;
    }

    Item(Group &group, const Glib::ustring &label);
    Item(Group               &group,
         const Color         &color,
         const Glib::ustring &label = Glib::ustring());
  };

private:
  Gtk::RadioButtonGroup group_;
  Color                 color_;

  Item none_;
  Item custom_;

protected:
  virtual void on_selected(const Color &color);
  virtual void custom_on_toggled();

public:
  ColorSelectMenu(Application &app, const Color &color);
};

/* Gtk::RadioMenuItem使うとなぜか破棄時にクラッシュするのでGtkRadioMenuItemを使う。 */
class ChSortMenu : public Gtk::Menu {
private:
  sigc::signal<void, ChSort> signal_changed_;
public:
  inline sigc::signal<void, ChSort> signal_changed() {
    return signal_changed_;
  }

private:
  GtkWidget *mi_none_;
  GtkWidget *mi_top_;
  GtkWidget *mi_middle_;
  GtkWidget *mi_bottom_;

  static void mi_on_toggled(GtkCheckMenuItem *mi, gpointer data);

protected:
  virtual void on_changed();

public:
  ChSort get();
  void set(ChSort sort);

  ChSortMenu();
};

class PlayerMenuItem : public Gtk::ImageMenuItem {
protected:
  typedef ConfMain::RefPlayer RefPlayer;

private:
  RefPlayer  player_;

  Gtk::Image image_;

  sigc::connection conn_icon_changed_;
  sigc::connection conn_auto_play_changed_;

protected:
  virtual void icon_on_changed();
  virtual void auto_play_on_changed();

public:
  inline RefPlayer get_player() {
    return player_;
  }

  PlayerMenuItem(RefPlayer player);
  virtual ~PlayerMenuItem();
};

class PlayWithMenu : public MenuBase {
public:
  typedef sigc::slot<void, CommandOpts &> SlotActivate;

protected:
  typedef ConfMain::RefPlayer RefPlayer;

  class Item : public PlayerMenuItem {
  private:
    Application &app_;
    SlotActivate slot_activate_;

  protected:
    virtual void on_activate();

    virtual void auto_play_on_changed();

  public:
    Item(Application &app, RefPlayer player, SlotActivate slot_activate);
  };

  struct PlayerMatch {
    inline bool operator()(Gtk::MenuItem &mi, RefPlayer player) {
      Item *i = dynamic_cast<Item *>(&mi);
      return i && i->get_player() == player;
    }
  };

private:
  Application &app_;
  SlotActivate slot_activate_;

  sigc::connection conn_item_added_;
  sigc::connection conn_item_removed_;
  sigc::connection conn_item_reordered_;
  sigc::connection conn_cleared_;

  SimpleMenuItem mi_setting_;
  DialogWrapper<pref::PlayerDialog> setting_dialog_;

protected:
  virtual void on_item_added(RefPlayer player);
  virtual void on_item_removed(RefPlayer player);
  virtual void on_item_reordered(RefPlayer player,
                                 RefPlayer prev,
                                 RefPlayer next);
  virtual void on_cleared();

private:
  void setting_on_clicked();

public:
  PlayWithMenu(Application &app,
               SlotActivate slot_activate = SlotActivate());
  virtual ~PlayWithMenu();
};

}/*gpyp*/

#endif/*MENU_H_*/
